package com.gitee.huanminabc.utils_tools.security.service.impl;

import com.gitee.huanminabc.jcommon.encryption.HashUtil;
import com.gitee.huanminabc.jcommon.exception.BizException;
import com.gitee.huanminabc.jcommon.exception.WebException;
import com.gitee.huanminabc.jcommon.encryption.JwtUtil;
import com.gitee.huanminabc.utils_tools.security.dal.redis.RedisSecurityService;
import com.gitee.huanminabc.utils_tools.security.enums.AuthEnum;
import com.gitee.huanminabc.utils_tools.security.param.LoginParam;
import com.gitee.huanminabc.utils_tools.security.service.ApplicationService;
import com.gitee.huanminabc.utils_tools.security.service.LoginService;
import com.gitee.huanminabc.utils_tools.security.service.UserDetailsService;
import com.gitee.huanminabc.utils_tools.security.vo.LoginUserVo;
import com.gitee.huanminabc.utils_tools.security.vo.TokenInfoVo;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.SignatureException;
import org.apache.logging.log4j.util.Strings;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.Objects;

@Service
public class LoginServiceImpl implements LoginService {

    @Autowired
    private AuthenticationManager authenticationManager;

    @Autowired
    private JwtUtil jwtUtil;
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private ApplicationService applicationService;
    @Autowired
    private RedisSecurityService redisSecurityService;

    @Override
    public TokenInfoVo login(LoginParam loginParam) throws Exception {
        // 认证,会去调用UserDetailsServiceImpl.loadUserByUsername
        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(loginParam.getAccount(), loginParam.getPassword());
        Authentication authenticate = authenticationManager.authenticate(authenticationToken);
        // 认证没通过
        if (Objects.isNull(authenticate)) {
            throw new UsernameNotFoundException("用户名或密码错误!");
        }
        // 认证通过 生成jwt
        LoginUserVo loginUserDto = (LoginUserVo) authenticate.getPrincipal();
        String jwt = jwtUtil.createJWT(loginUserDto.getUsername(), loginUserDto.getPassword(), JwtUtil.JwtEnum.ACCESS);
        //生成refreshToken
        String refreshToken = jwtUtil.createJWT(loginUserDto.getUsername(), loginUserDto.getPassword(), JwtUtil.JwtEnum.REFRESH);

        // 认证通过 存入 redis
        //+10秒 保证在refreshToken过期之后,不然有可能验证通过,但是redis中的数据已经过期了
        redisSecurityService.setLoginUserDto(loginUserDto,(jwtUtil.getRefreshExpireMillis() / 1000)+10);


        TokenInfoVo tokenInfoDto = new TokenInfoVo();
        tokenInfoDto.setToken(jwt);
        tokenInfoDto.setRefreshToken(refreshToken);
        return tokenInfoDto;
    }

    @Override
    public boolean logout(String account) {
        // 清空redis
        redisSecurityService.deleteLoginUserDto(account);
        return true;
    }

    @Override
    public String refreshToken(String refreshToken) {

        // 没有token
        if (!StringUtils.hasText(refreshToken)) {
            throw new WebException(AuthEnum.NO_REFRESH_TOKEN.getCode(),AuthEnum.NO_REFRESH_TOKEN.getMsg());
        }
        //拿refreshToken换新token
        String account=null;
        try {
            account = jwtUtil.getUserNameFromToken(refreshToken, JwtUtil.JwtEnum.REFRESH);
        } catch (SignatureException e) {
            throw new WebException(AuthEnum.ILLEGAL_TOKEN.getCode(),AuthEnum.ILLEGAL_TOKEN.getMsg());
        } catch (ExpiredJwtException e) {//refreshToken过期
            throw new WebException(AuthEnum.REFRESH_EXPIRED.getCode(),AuthEnum.REFRESH_EXPIRED.getMsg());
        }
        LoginUserVo loginUserDto = (LoginUserVo)userDetailsService.loadUserByUsername(account);
        // 认证通过 生成新jwt
        try {
            return jwtUtil.createJWT(loginUserDto.getUsername(), loginUserDto.getPassword(), JwtUtil.JwtEnum.ACCESS);
        } catch (Exception e) {
            throw new BizException(e);
        }
    }


    /**
     * 拿AccessToken换新用户信息
     * @param accessToken
     * @return
     */
    @Override
    public LoginUserVo getTokenToUser(String accessToken) {
        String account=null;
        try {
            account = jwtUtil.getUserNameFromToken(accessToken, JwtUtil.JwtEnum.ACCESS);
        } catch (SignatureException e) { //错误的token
            throw new BizException(AuthEnum.ILLEGAL_TOKEN.getMsg());
        } catch (ExpiredJwtException e) {//过期的token
            throw new BizException(AuthEnum.REFRESH_EXPIRED.getMsg());
        }
        return redisSecurityService.getLoginUserDto(account);
    }

    @Override
    public String createMockToken(String account,String appId,String appSecret) throws Exception {
        //验证是否已经生成过token,如果生成过,直接返回
        String hash = HashUtil.md5(account + appId + appSecret);
        String mockToken= redisSecurityService.getMockToken(hash);
        if (Strings.isNotBlank(mockToken)){//在refresh时间范围, 已经生成过token,直接返回新的token不用验证, 大概4小时左右
            return mockToken;
        }
        //验证name,appId和appSecret
       if ( !applicationService.hasPermission(account, appId, appSecret)){
              throw new BizException("用户或appId或appSecret错误,请联系管理员!");
       }
        //获取用户信息
        LoginUserVo userDetails =(LoginUserVo) userDetailsService.loadUserByUsername(account);
        String jwt = jwtUtil.createJWT(userDetails.getUsername(), userDetails.getPassword(), JwtUtil.JwtEnum.CUSTOM);
        // 认证通过 存入用户信息到 redis, 不然其他接口拿不到redis用户信息会报错的
        //+10秒 保证在refreshToken过期之后,不然有可能验证通过,但是redis中的数据已经过期了
        redisSecurityService.setLoginUserDto(userDetails, (jwtUtil.getRefreshExpireMillis() / 1000)+10);
        //将已经认证过的,name和appId和appSecret存入redis
        //用于反复获取token,不用每次都去数据库查用户信息验证, 需要比refreshToken的时间短10s保证能拿到用户信息
        redisSecurityService.setMockToken(hash,jwt,(jwtUtil.getRefreshExpireMillis() / 1000)) ;
        return jwt;
    }

}
